ANDROID: ashmem: Handle ASHMEM_GET_FILE_ID from 32-bit userspace programs

ASHMEM_GET_FILE_ID is defined as taking a pointer to an unsigned long as
an input parameter. This is unfortunate, as the size of an unsigned long
is different across 64 and 32-bit programs, and consequently so is the
definition of the ioctl command.

So, when invoked from a process executing a 32-bit program on a 64-bit
kernel, the ioctl command is not recognized. Therefore, define the 32-bit
version of ASHMEM_GET_FILE_ID and handle it accordingly.

While we're here, fix the ashmem-memfd handler so that it returns ENOTTY
when an unknown ioctl command is used.

Bug: 502308977
Fixes: 80fcf382d1af ("ANDROID: Add ashmem ioctl to return a unique file identifier")
Change-Id: I016b86218c43fa808215342eac85591cc037de9b
Signed-off-by: Isaac J. Manjarres <isaacmanjarres@google.com>
diff --git a/drivers/staging/android/ashmem.h b/drivers/staging/android/ashmem.h
index 373437f..49f234c35 100644
--- a/drivers/staging/android/ashmem.h
+++ b/drivers/staging/android/ashmem.h
@@ -28,6 +28,7 @@ const gfp_t RUST_CONST_HELPER___GFP_IO = ___GFP_IO;
 enum {
 	COMPAT_ASHMEM_SET_SIZE		=	_IOW(__ASHMEMIOC, 3, compat_size_t),
 	COMPAT_ASHMEM_SET_PROT_MASK	=	_IOW(__ASHMEMIOC, 5, unsigned int),
+	COMPAT_ASHMEM_GET_FILE_ID	=	_IOR(__ASHMEMIOC, 11, compat_uptr_t),
 };
 #endif
 
diff --git a/drivers/staging/android/ashmem.rs b/drivers/staging/android/ashmem.rs
index 7e2e0bf..080716a 100644
--- a/drivers/staging/android/ashmem.rs
+++ b/drivers/staging/android/ashmem.rs
@@ -79,6 +79,13 @@ fn shrinker_should_stop() -> bool {
     NUM_PIN_IOCTLS_WAITING.load(Ordering::Relaxed) > 0
 }
 
+#[cfg(CONFIG_COMPAT)]
+fn compat_writer(compat_user_ptr: u32, size: usize) -> UserSliceWriter {
+    // SAFETY: Caller guarantees that this is a valid pointer.
+    let user_ptr = unsafe { bindings::compat_ptr(compat_user_ptr) };
+    UserSlice::new(user_ptr as usize, size).writer()
+}
+
 module! {
     type: AshmemModule,
     name: "ashmem",
@@ -277,6 +284,9 @@ fn compat_ioctl(me: Pin<&Ashmem>, file: &File, compat_cmd: u32, arg: usize) -> R
         let cmd = match compat_cmd {
             bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE,
             bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK,
+            bindings::COMPAT_ASHMEM_GET_FILE_ID => {
+                return me.compat_get_file_id(compat_writer(arg as u32, _IOC_SIZE(compat_cmd)))
+            }
             _ => compat_cmd,
         };
         Self::ioctl(me, file, cmd, arg)
@@ -374,18 +384,27 @@ fn get_prot_mask(&self) -> Result<isize> {
         Ok(self.inner.lock().prot_mask as isize)
     }
 
-    fn get_file_id(&self, mut writer: UserSliceWriter) -> Result<isize> {
-        let ino = {
-            let asma = self.inner.lock();
-            let Some(file) = asma.file.as_ref() else {
-                return Err(EINVAL);
-            };
-            file.inode_ino()
+    fn __get_file_id(&self) -> Result<usize> {
+        let asma = self.inner.lock();
+        let Some(file) = asma.file.as_ref() else {
+            return Err(EINVAL);
         };
+        Ok(file.inode_ino())
+    }
+
+    fn get_file_id(&self, mut writer: UserSliceWriter) -> Result<isize> {
+        let ino = self.__get_file_id()?;
         writer.write(&ino)?;
         Ok(0)
     }
 
+    #[cfg(CONFIG_COMPAT)]
+    fn compat_get_file_id(&self, mut writer: UserSliceWriter) -> Result<isize> {
+        let ino = self.__get_file_id()?;
+        writer.write(&(ino as u32))?;
+        Ok(0)
+    }
+
     fn pin_unpin(&self, cmd: u32, mut reader: UserSliceReader) -> Result<isize> {
         let (offset, cmd_len) = {
             #[allow(dead_code)] // spurious warning because it is never explicitly constructed
@@ -513,18 +532,28 @@ impl AshmemInner {
 
 #[no_mangle]
 unsafe extern "C" fn ashmem_memfd_ioctl(file: *mut bindings::file, cmd: u32, arg: usize) -> isize {
-    #[cfg(CONFIG_COMPAT)]
-    let cmd = match cmd {
-        bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE,
-        bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK,
-        cmd => cmd,
-    };
-
     // SAFETY:
     // * The file is valid for the duration of this call.
     // * There is no active fdget_pos region on the file on this thread.
     let file = unsafe { File::from_raw_file(file) };
 
+    #[cfg(CONFIG_COMPAT)]
+    let cmd = match cmd {
+        bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE,
+        bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK,
+        bindings::COMPAT_ASHMEM_GET_FILE_ID => {
+            // SAFETY: Accessing the ino is always okay.
+            let ino = unsafe { (*(*file.as_ptr()).f_inode).i_ino as usize };
+
+            let mut writer = compat_writer(arg as u32, _IOC_SIZE(cmd));
+            match writer.write(&(ino as u32)) {
+                Ok(_) => return 0,
+                Err(err) => return err.to_errno() as isize,
+            };
+        }
+        cmd => cmd,
+    };
+
     match ashmem_memfd_ioctl_inner(file, cmd, arg) {
         Ok(ret) => ret,
         Err(err) => err.to_errno() as isize,
@@ -635,7 +664,7 @@ fn get_seals(file: &File) -> Result<usize> {
         // memfd is used, so we should never end up here.
         bindings::ASHMEM_SET_NAME => Err(EINVAL),
         bindings::ASHMEM_SET_SIZE => Err(EINVAL),
-        _ => Err(EINVAL),
+        _ => Err(ENOTTY),
     }
 }
 
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 648007a..7a57852 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -11,6 +11,7 @@
 #include <linux/blk-mq.h>
 #include <linux/blk_types.h>
 #include <linux/blkdev.h>
+#include <linux/compat.h>
 #include <linux/cred.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
diff --git a/rust/helpers/compat.c b/rust/helpers/compat.c
new file mode 100644
index 0000000..55bf2a8a
--- /dev/null
+++ b/rust/helpers/compat.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/compat.h>
+
+#ifdef CONFIG_COMPAT
+__rust_helper void __user *rust_helper_compat_ptr(compat_uptr_t uptr)
+{
+	return compat_ptr(uptr);
+}
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 7ea3655..2dd2866 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -45,6 +45,7 @@
 #include "bug.c"
 #include "build_assert.c"
 #include "build_bug.c"
+#include "compat.c"
 #include "cred.c"
 #include "err.c"
 #include "fs.c"